//	VirtualDub - Video processing and capture application
//	Copyright (C) 1998-2004 Avery Lee
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include <stdafx.h>
#include <stdafx.h>

#include <vd2/system/w32assist.h>
#include <vd2/Dita/w32base.h>
#include <set>

VDUIBaseWindowW32::VDUIBaseWindowW32()
	: mNextNativeID(1337)
	, mpCB(NULL)
	, mbCBAutoDelete(false)
	, mbAutoDestroy(false)
	, mbTick(false)
	, mpModal(NULL)
{
}

VDUIBaseWindowW32::~VDUIBaseWindowW32() {
	if (mpCB && mbCBAutoDelete)
		delete mpCB;
}

void *VDUIBaseWindowW32::AsInterface(uint32 id) {
	switch(id) {
		case IVDUIBase::kTypeID: return static_cast<IVDUIBase *>(this); break;
	}

	return VDUICustomControlW32::AsInterface(id);
}

void VDUIBaseWindowW32::Shutdown() {
	DispatchEvent(this, mID, IVDUICallback::kEventDestroy, 0);
	SetCallback(NULL, false);
	VDUICustomControlW32::Shutdown();
}

bool VDUIBaseWindowW32::Create(IVDUIParameters *pParams) {
	bool is_child = pParams->GetB(nsVDUI::kUIParam_Child, false);

	mPadding = pParams->GetI(nsVDUI::kUIParam_Spacing, is_child ? 0 : 7);

	DWORD flags = 0;

	if (pParams->GetB(nsVDUI::kUIParam_SystemMenus, false))
		flags |= WS_OVERLAPPEDWINDOW;

	return VDUICustomControlW32::Create(pParams, !is_child, flags);
}

void VDUIBaseWindowW32::Destroy() {
	if (VDINLINEASSERTFALSE(mpModal)) {
		EndModal(-1);
		return;
	}

	VDUICustomControlW32::Destroy();

	if (mbAutoDestroy) {
		mbAutoDestroy = false;
		if (mpParent)
			mpParent->RemoveChild(this);
		Release();
	}
}

void VDUIBaseWindowW32::SetAutoDestroy(bool enable) {
	if (enable)
		AddRef();
	if (mbAutoDestroy)
		Release();
	mbAutoDestroy = enable;
}

void VDUIBaseWindowW32::SetTickEnable(bool enable) {
	mbTick = enable;
}

vduirect VDUIBaseWindowW32::MapUnitsToPixels(vduirect r) {
	RECT rwin = {r.left, r.top, r.right, r.bottom};

	MapDialogRect(mhwnd, &rwin);

	return vduirect(rwin.left, rwin.top, rwin.right, rwin.bottom);
}

vduisize VDUIBaseWindowW32::MapUnitsToPixels(vduisize s) {
	RECT rwin = {0,0,s.w,s.h};

	MapDialogRect(mhwnd, &rwin);

	return vduisize(rwin.right, rwin.bottom);
}

void VDUIBaseWindowW32::AddControl(IVDUIWindow *pWin) {
	const uint32 id = pWin->GetID();
	std::pair<tControls::iterator, tControls::iterator> result(mControls.equal_range(id));

	for(; result.first != result.second; ++result.first)
		if ((*result.first).second == pWin)
			return;

	mControls.insert(result.first, tControls::value_type(id, pWin));
}

void VDUIBaseWindowW32::RemoveControl(IVDUIWindow *pWin) {
	const uint32 id = pWin->GetID();
	std::pair<tControls::iterator, tControls::iterator> result(mControls.equal_range(id));

	while(result.first != result.second)
		if ((*result.first).second == pWin)
			mControls.erase(result.first++);
		else
			++result.first;
}

IVDUIWindow *VDUIBaseWindowW32::GetControl(uint32 id) {
	tControls::iterator it(mControls.find(id));

	if (it != mControls.end())
		return (*it).second;

	return NULL;
}

uint32 VDUIBaseWindowW32::GetNextNativeID() {
	return mNextNativeID++;
}

void VDUIBaseWindowW32::SetCallback(IVDUICallback *pCB, bool autoDelete) {
	if (mpCB) {
		DispatchEvent(this, GetID(), IVDUICallback::kEventDetach, 0);
		if (mbCBAutoDelete)
			delete mpCB;
	}
	mpCB = pCB;
	mbCBAutoDelete = autoDelete;
	DispatchEvent(this, GetID(), IVDUICallback::kEventAttach, 0);
}

void VDUIBaseWindowW32::FinalizeDialog() {
	Relayout();

	if (mhwnd && GetFocus() == mhwnd) {
		// must init focus to a control or else shortcuts don't work at first
		HWND hwndFirstFocus = GetNextDlgTabItem(mhwnd, NULL, FALSE);
		if (hwndFirstFocus)
			::SetFocus(hwndFirstFocus);
	}
}

////////////////////////////////////////////////////////////////////////////

struct VDUIBaseWindowW32::ModalData {
	int		mReturnValue;
	bool	mbOwnerWasEnabled;
	HWND	mhwndOwner;
};

int VDUIBaseWindowW32::DoModal() {
	if (VDINLINEASSERTFALSE(mpModal))
		return -1;

	ModalData data;

	mpModal = &data;

	HWND hwndParent = GetParentW32();		// Cannot use GetParent() as it isn't correct until after messages have been dispatched and the window hops to its real parent.

	data.mbOwnerWasEnabled = false;
	data.mhwndOwner = hwndParent;

	if (data.mhwndOwner) {
		data.mbOwnerWasEnabled = !(GetWindowLong(data.mhwndOwner, GWL_STYLE) & WS_DISABLED);

		if (data.mbOwnerWasEnabled)
			EnableWindow(data.mhwndOwner, FALSE);
	}

	MSG msg;
	while(mpModal) {
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
			if (msg.message == WM_QUIT) {
				PostQuitMessage(msg.wParam);
				break;
			}

			if (IsDialogMessage(mhwnd, &msg))
				continue;
			TranslateMessage(&msg);
			DispatchMessage(&msg);
			continue;
		}

		if (!mpModal)
			break;

		if (mbTick) {
			if (mpCB && mpCB->HandleUIEvent(this, NULL, 0, IVDUICallback::kEventTick, 0))
				continue;
		}

		WaitMessage();
	}

	mpModal = NULL;

	Shutdown();

	return data.mReturnValue;
}

void VDUIBaseWindowW32::EndModal(int value) {
	if (VDINLINEASSERTFALSE(!mpModal))
		return;

	if (mpModal->mhwndOwner && mpModal->mbOwnerWasEnabled)
		EnableWindow(mpModal->mhwndOwner, TRUE);

	if (mhwnd) {
		// Set the popup flag on the window so it has a valid parent. This is needed so
		// that on destruction the window manager reactivates the owner.
		SetWindowLong(mhwnd, GWL_STYLE, GetWindowLong(mhwnd, GWL_STYLE) | WS_POPUP);
	}

	mpModal->mReturnValue = value;
	mpModal = NULL;
}

void VDUIBaseWindowW32::Link(uint32 id, nsVDUI::LinkTarget target, const uint8 *src, size_t len) {
	int expressions = *src++;

	while(expressions-- > 0) {
		tLinkList::iterator it(mLinkList.insert(tLinkList::value_type(id, LinkEntry())));
		LinkEntry& linkEnt = (*it).second;

		int refs = *src++;
		linkEnt.mLinkSources.reserve(refs);

		while(refs-->0) {
			const uint32 id = src[0] + ((uint32)src[1]<<8) + ((uint32)src[2]<<16) + ((uint32)src[3]<<24);
			src += 4;

			linkEnt.mLinkSources.push_back(id);
		}

		const int bclen = src[0] + ((uint32)src[1]<<8);
		src += 2;
		linkEnt.mByteCode.assign(src, src+bclen+1);
		src += bclen+1;
		linkEnt.mTarget = target;
	}

	mbLinkUpdateMapDirty = true;
}

void VDUIBaseWindowW32::ProcessActivation(IVDUIWindow *pWin, uint32 id) {
	ExecuteLinks(id);
}

void VDUIBaseWindowW32::ProcessValueChange(IVDUIWindow *pWin, uint32 id) {
	ExecuteLinks(id);
}

bool VDUIBaseWindowW32::DispatchEvent(IVDUIWindow *pWin, uint32 id, IVDUICallback::eEventType ev, int item) {
	if (mpCB)
		return mpCB->HandleUIEvent(this, pWin, id, ev, item);

	return false;
}

LRESULT VDUIBaseWindowW32::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) {
	switch(msg) {
		case WM_INITDIALOG:
			{
				RECT r;
				GetWindowRect(mhwnd, &r);
				MapWindowPoints(mhwnd, ::GetParent(mhwnd), (LPPOINT)&r, 2);
				mArea.left = r.left;
				mArea.top = r.top;
				mArea.right = r.right;
				mArea.bottom = r.bottom;

				DispatchEvent(this, mID, IVDUICallback::kEventCreate, 0);
				if (!(GetWindowLong(mhwnd, GWL_STYLE) & DS_CONTROL)) {
					Relayout();
				}
				ExecuteAllLinks();
			}
			return FALSE;
   
		case WM_GETMINMAXINFO:
			{
				MINMAXINFO *pmmi = (MINMAXINFO *)lParam;
   
				pmmi->ptMinTrackSize.x = mLayoutSpecs.minsize.w;
				pmmi->ptMinTrackSize.y = mLayoutSpecs.minsize.h;
   
				if ((mAlignX & nsVDUI::kAlignTypeMask) != nsVDUI::kFill)
					pmmi->ptMaxTrackSize.x = mLayoutSpecs.minsize.w;
   
				if ((mAlignY & nsVDUI::kAlignTypeMask) != nsVDUI::kFill)
					pmmi->ptMaxTrackSize.y = mLayoutSpecs.minsize.h;
			}
			return 0;

		case WM_SIZE:
			if (!(GetWindowLong(mhwnd, GWL_STYLE) & WS_CHILD)) {
				vduirect r(GetClientArea());

				if (r.size() != GetArea().size()) {
					r.left   += mInsets.left;
					r.top    += mInsets.top;
					r.right  -= mInsets.right;
					r.bottom -= mInsets.bottom;

					tChildren::iterator it(mChildren.begin()), itEnd(mChildren.end());

					for(; it!=itEnd; ++it) {
						IVDUIWindow *pWin = *it;

						pWin->PostLayout(r);
					}
				}
			}
			return 0;

		case WM_CLOSE:
			if (DispatchEvent(this, mID, IVDUICallback::kEventClose, 0))
				return 0;
			goto handle_cancel;

		case WM_NOTIFYFORMAT:
			return VDIsWindowsNT() ? NFR_UNICODE : NFR_ANSI;

		case WM_COMMAND:
			// special handling for commands that have have keyboard equivalents in
			// the dialog manager; we never assign these native IDs so it's safe to
			// shortcut them
			if (LOWORD(wParam) == IDOK) {
				DispatchEvent(this, 10, IVDUICallback::kEventSelect, 0);
				return 0;
			} else if (LOWORD(wParam) == IDCANCEL) {
handle_cancel:
				if (!DispatchEvent(this, 11, IVDUICallback::kEventSelect, 0)) {
					if (mpModal)
						EndModal(-1);
					else
						Shutdown();
				}
				return 0;
			} else if (!lParam) {
				// dispatch menu/accelerator commands
				DispatchEvent(this, LOWORD(wParam), IVDUICallback::kEventSelect, 0);
				return 0;
			}
			break;
	}

	return VDUICustomControlW32::WndProc(msg, wParam, lParam);
}

void VDUIBaseWindowW32::Relayout() {
	VDUILayoutSpecs constraints;

	constraints.minsize.w = GetSystemMetrics(SM_CXMAXIMIZED);
	constraints.minsize.h = GetSystemMetrics(SM_CYMAXIMIZED);

	PreLayout(constraints);

	vduirect r(GetArea());

	if ((mAlignX & nsVDUI::kAlignTypeMask) != nsVDUI::kFill) {
		if (r.width() > mLayoutSpecs.minsize.w)
			r.right = r.left + mLayoutSpecs.minsize.w;
	}

	if ((mAlignY & nsVDUI::kAlignTypeMask) != nsVDUI::kFill) {
		if (r.height() > mLayoutSpecs.minsize.h)
			r.bottom = r.top + mLayoutSpecs.minsize.h;
	}

	PostLayout(r);
}

void VDUIBaseWindowW32::PreLayoutBase(const VDUILayoutSpecs& parentConstraints) {
	VDUILayoutSpecs rcConstraints(parentConstraints);
	RECT rcBorders = {0,0,0,0};
	RECT rcPad = {mPadding,mPadding,mPadding,mPadding};
	bool bModeless = (0 != (GetWindowLong(mhwnd, GWL_STYLE) & DS_CONTROL));

	// If the dialog is modal, compute the insets for the dialog.

	if (!bModeless) {
		MapDialogRect(mhwnd, &rcPad);

		mInsets = rcPad;

		AdjustWindowRectEx(&rcBorders, GetWindowLong(mhwnd, GWL_STYLE), GetMenu(mhwnd) != NULL, GetWindowLong(mhwnd, GWL_STYLE));

		// enlarge borders by pads
		rcBorders.left		= rcBorders.left   - rcPad.left;
		rcBorders.top		= rcBorders.top    - rcPad.top;
		rcBorders.right		= rcBorders.right  + rcPad.right;
		rcBorders.bottom	= rcBorders.bottom + rcPad.bottom;
	} else
		mInsets = rcBorders;

	// Shrink constraints by insets.

	mLayoutSpecs.minsize.w = rcBorders.right - rcBorders.left;
	mLayoutSpecs.minsize.h = rcBorders.bottom - rcBorders.top;

	rcConstraints.minsize.w -= mLayoutSpecs.minsize.w;
	rcConstraints.minsize.h -= mLayoutSpecs.minsize.h;

	// Layout children.

	tChildren::iterator it(mChildren.begin()), itEnd(mChildren.end());
	vduisize minsize(0, 0);

	for(; it!=itEnd; ++it) {
		IVDUIWindow *pWin = *it;

		pWin->PreLayout(rcConstraints);

		const VDUILayoutSpecs& prispecs = pWin->GetLayoutSpecs();

		if (minsize.w < prispecs.minsize.w)
			minsize.w = prispecs.minsize.w;
		if (minsize.h < prispecs.minsize.h)
			minsize.h = prispecs.minsize.h;
	}

	mLayoutSpecs.minsize.w += minsize.w;
	mLayoutSpecs.minsize.h += minsize.h;
}

void VDUIBaseWindowW32::PostLayoutBase(const vduirect& target) {
	VDUIControlW32::PostLayoutBase(target);

	vduirect rc(GetClientArea());

	rc.left += mInsets.left;
	rc.top += mInsets.top;
	rc.right -= mInsets.right;
	rc.bottom -= mInsets.bottom;

	tChildren::iterator it(mChildren.begin()), itEnd(mChildren.end());
	vduisize minsize(0, 0);

	for(; it!=itEnd; ++it) {
		IVDUIWindow *pWin = *it;

		pWin->PostLayout(rc);
	}
}

void VDUIBaseWindowW32::RebuildLinkUpdateMap() {
	if (!mbLinkUpdateMapDirty)
		return;

	mbLinkUpdateMapDirty = false;

	mLinkUpdateMap.clear();
	for(tLinkList::const_iterator it(mLinkList.begin()), itEnd(mLinkList.end()); it != itEnd; ++it) {
		const tLinkList::value_type& data = *it;
		const LinkEntry& linkEntry = data.second;

		for(std::vector<uint32>::const_iterator itLink(linkEntry.mLinkSources.begin()), itLinkEnd(linkEntry.mLinkSources.end()); itLink != itLinkEnd; ++itLink) {
			const uint32 sourceID = *itLink;

			mLinkUpdateMap.insert(tLinkUpdateMap::value_type(sourceID, &data));
		}
	}
}

void VDUIBaseWindowW32::ExecuteLinks(uint32 id) {
	std::list<uint32> queue;
	queue.push_back(id);

	ExecuteLinks(queue);
}

void VDUIBaseWindowW32::ExecuteAllLinks() {
	uint32 last = 0;

	std::list<uint32> queue;
	for(tControls::const_iterator it(mControls.begin()), itEnd(mControls.end()); it!=itEnd; ++it) {
		uint32 id = (*it).first;

		if (last != id) {
			last = id;

			queue.push_back(id);
		}
	}

	ExecuteLinks(queue);
}

void VDUIBaseWindowW32::ExecuteLinks(std::list<uint32>& queue) {
	if (mbLinkUpdateMapDirty)
		RebuildLinkUpdateMap();

	std::set<uint32> visited;
	while(!queue.empty()) {
		uint32 id = queue.front();
		queue.pop_front();

		std::pair<tLinkUpdateMap::iterator, tLinkUpdateMap::iterator> links(mLinkUpdateMap.equal_range(id));
		std::vector<IVDUIWindow *> srcWindows;

		for(; links.first != links.second; ++links.first) {
			const tLinkList::value_type *pLinkListEntry = (*links.first).second;
			const uint32 targetID = pLinkListEntry->first;
			const LinkEntry& linkEntry = pLinkListEntry->second;
			const int sources = linkEntry.mLinkSources.size();

			IVDUIWindow *pTarget = GetControl(targetID);

			if (!pTarget)
				continue;

			if (visited.insert(targetID).second)
				queue.push_back(targetID);

			srcWindows.resize(sources);
			for(int i=0; i<sources; ++i)
				srcWindows[i] = GetControl(linkEntry.mLinkSources[i]);

			const uint32 result = VDUIExecuteRuntimeExpression(&linkEntry.mByteCode.front(), &srcWindows.front());

			switch(linkEntry.mTarget) {
				case nsVDUI::kLinkTarget_Enable:
					pTarget->SetEnabled(result != 0);
					break;

				case nsVDUI::kLinkTarget_Value:
					pTarget->SetValue(result);
					break;
			}
		}
	}
}
